// 手写 call、apply、bind,并说明之间的区别

// call
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
// 该方法的语法和作用与 apply() 方法类似，只有一个区别，就是 call() 方法接受的是一个参数列表，而 apply() 方法接受的是一个包含多个参数的数组。
Function.prototype.myCall = function(context,...args) {
	// 判断传入的参数是否是 undefined 或者是 null
	if(typeof context === 'undefined' || context === null) {
		context = window
	}
	// 声明一个symbol
	let fnSymbol = Symbol()
	// 为 context 添加一个新的属性，键名为一个symbol,因为每一个Symbol都是独一无二的，不会影响到 context 本身的属性；键值为调用 call 方法的函数，也就是 this。这样，context就有了一个新的属性 symbol,里面保存的是一个函数方法，这样，在调用函数方法时，就可以访问到当前 context 中的所有属性了
	// 这里的 this 指的是调用call方法的函数，通过新的属性 fnSymbol，将函数添加到新的上下文中，然后调用
	context[fnSymbol] = this
	let fn = context[fnSymbol](...args)
	// 执行完时删除 context 的新增属性 fnSymbol
	delete context[fnSymbol]
	// 最终返回执行的结果
	return fn
}

var Jerry = {
  name: 'Jerry',
  word: 'hello 666',
  to: 'Jams'
}

function sayFnName (to, word) {
    console.log(`myCall方法:::${this.name} say ${this.word} to ${this.to}`)
    let arg1 = Array.prototype.slice.myCall(arguments, 1)
    console.log(arg1)
}

sayFnName.myCall(Jerry)
sayFnName(123,456)

// apply 与上边的call没啥区别，就是传参的格式不一样，apply传参的格式为数组
Function.prototype.myApply = function(context, args){
	// 首先判断传入的 context 参数的类型
	if(typeof context === 'undefined' || context === null) {
		context = window
	}
	// 声明一个唯一的 key 键 Symbol
	const fnSymbol = Symbol()
	// 为当前的 context 添加一个新的属性 fnSymbol,属性值为调用 call 方法的函数
	context[fnSymbol] = this
	// 执行函数方法，并传入参数
	let fn = context[fnSymbol](...args)
	// 删除当前 context 中的新添加的 fnSymbol 属性
	delete context[fnSymbol]
	// 最终返回函数的执行结果 fn
	return fn
}

var Foo = {
  name: 'Foo',
  word: 'school',
  to: 'China'
}

function textFn(to, word) {
    console.log(`myApply方法:::${this.name} say ${word} to ${to}`)
}

textFn.myApply(Foo, ['play the games', 'I like'])

// bind方法创建一个新的函数，在bind()方法被调用时，新函数的 this 被指定为 bind() 方法的第一个参数，而其余参数将作为新函数的参数，供调用时使用

Function.prototype.myBind = function(...args){
	// 保存 this,指的是调用bind方法的对象，这里的 this 指的是 test方法
	let self = this
	// 取出第一个参数，也就是this的指向对象
	let obj = args[0] // 获取传入参数的 this 指向
	if(typeof obj === 'object'){
		obj = obj || window
	} else {
		obj = Object.create(null)
	}
	// 剩余的参数保存起来
	const params1 = args.slice(1)
	// 返回一个待执行的函数
	return function(...args1){
		// 合并参数
		let params2 = params1.concat(args1)
		// 调用 apply 方法进行 this 转换
		return self.apply(obj, params2)
	}
}

var XYZ = {
  name: 'testMyBindFn'
}

function testBind(num,num1){
	console.log(`myBindFn:::${this.name} say arguments is ${num} and ${num1}`)
}

var say = testBind.myBind(XYZ, '666')
say('ABC')